package com.tannm.doan.widgets;

import java.util.*;
import android.content.*;
import android.view.*;
import android.widget.LinearLayout.LayoutParams;
import android.graphics.*;

public class TimeWidgetSlider extends View {
	// types
	public interface OnTimeChange {
		public void OnChange(TimeWidgetSlider slider);
	}

	// types
	private class ValueBox {
		// fields
		public int iValue = 0;
		@SuppressWarnings("unused")
		public int iLeftOffset = 0;
		@SuppressWarnings("unused")
		public int iWidth = 0;
		public String strValue = null;
		public int iStrWidth = 0;
		public int iStrWidthSmall = 0;

		// methods
		public ValueBox(int iValue, int iLeftOffset, int iWidth, Paint pt) {
			this.iValue = iValue;
			this.iLeftOffset = iLeftOffset;
			this.iWidth = iWidth;
			strValue = getValueStr();
			calcValueStrWidths(pt);
		}

		private String getValueStr() {
			if (iSliderType == STYPE_MINUTES)
				return Integer.toString(iValue * 5);
			if (iSliderType == STYPE_HOURS) {
				if (b24HourMode) {
					return Integer.toString(iValue);
				} else {
					int iDisplayHour = iValue;
					if (iDisplayHour == 0)
						iDisplayHour = 12;
					if (iDisplayHour > 12)
						iDisplayHour -= 12;
					return Integer.toString(iDisplayHour);
				}
			}
			return "";
		}

		public void calcValueStrWidths(Paint pt) {
			pt.setFakeBoldText(true);
			pt.setTypeface(tfMono);

			pt.setTextSize(fTextSize);
			iStrWidth = (int) pt.measureText(strValue);

			pt.setTextSize(fTextSizeSmall);
			iStrWidthSmall = (int) pt.measureText(strValue);
		}

		@SuppressWarnings("unused")
		public boolean isPM() {
			if (iValue >= 12)
				return true;
			return false;
		}
	}

	// fields
	private ArrayList<ValueBox> vecValues = new ArrayList<ValueBox>();

	// fields
	private final static int iColorBkg = 0xff777777;
	private final static int iColorLightBkg = 0xffcccccc;
	private final static int iColorSliderBkg = 0xff444444;
	private final static int iColorButtonArrows = 0xff444444;
	private final static int iColorSliderCenterBox = 0xffffffff;
	private final static int iColorValueBoxText = 0xff666666;
	private final static int iColorValueBoxBkg = 0xffbbbbbb;
	private final static int iColorValueBoxTextSelected = 0xff222222;

	// fields
	private final static Typeface tfMono = Typeface.create(Typeface.MONOSPACE,
			Typeface.NORMAL);
	private final static float fTextSize = 28;
	private final static float fTextSizeSmall = 22;
	private final static int iFocusBorder = 3;
	private final static int iSliderSpace = 4;
	private final static int iSliderBorder = 4;
	private final static int iBoxBorder = 4;
	private final static int iSliderButtonWidth = 40;

	// fields
	protected OnTimeChange mOnTimeChange = null;

	// fields
	private Paint pt = new Paint();
	private RectF rectFocus = new RectF();
	private RectF rect = new RectF();
	private RectF rectBar = new RectF();
	private RectF rectBox = new RectF();
	private RectF rectCenterBox = new RectF();
	private RectF rectButtonLeft = new RectF();
	private RectF rectButtonRight = new RectF();
	private RectF rectSlider = new RectF();

	// fields
	private boolean b24HourMode = false;
	private int iSliderType = 0;
	private boolean bNoValue = false;
	private int iCurrentValue = -1;

	// fields
	private boolean bTouchedDownSlider = false;
	private boolean bTouchedDownBtnLeft = false;
	private boolean bTouchedDownBtnRight = false;
	private int iTouchedSliderPosX = 0;

	// fields
	public final static int STYPE_HOURS = 1;
	public final static int STYPE_MINUTES = 2;

	// fields
	private int iBoxWidth = 0;
	private BlurMaskFilter filterBlur = new BlurMaskFilter(0.5F,
			BlurMaskFilter.Blur.NORMAL);

	// fields
	private int iAnimPosX = 0;
	private int iAnimDelta = 0;
	private long mLastTime = 0;
	private final static int iAnimStep = 12;

	// methods
	public TimeWidgetSlider(Context context, boolean b24HourMode,
			int iSliderType, int iWidth) {
		super(context);
		this.b24HourMode = b24HourMode;
		this.iSliderType = iSliderType;
		setFocusable(true);

		final int iHeight = getTotalHeight();
		setLayoutParams(new LayoutParams(iWidth, iHeight));

		// init sizes
		iBoxWidth = getBoxWidth();
		initRectangles(iWidth, iHeight);

		generateValues();
		setValue(getValue(), false);
	}

	public void setTimeChangeEvent(OnTimeChange timeChange) {
		this.mOnTimeChange = timeChange;
	}

	public void doTimeChanged() {
		if (mOnTimeChange != null)
			mOnTimeChange.OnChange(this);
	}

	private int getBoxWidth() {
		pt.setTypeface(tfMono);
		pt.setTextSize(fTextSize);
		pt.setFakeBoldText(true);
		return iBoxBorder + (int) pt.measureText("00") + iBoxBorder;
	}

	private void initRectangles(int iWidth, int iHeight) {
		rectFocus.set(0, 0, iWidth, iHeight);

		rect.set(rectFocus);
		rect.inset(0, iFocusBorder);

		rectBar.set(iFocusBorder, iFocusBorder + iSliderBorder + iSliderSpace,
				iWidth - iFocusBorder, iHeight - iSliderSpace - iSliderBorder
						- iFocusBorder);

		rectButtonLeft.set(rect);
		rectButtonLeft.right = iSliderButtonWidth;
		rectButtonRight.set(rect);
		rectButtonRight.left = rectButtonRight.right - iSliderButtonWidth;

		rectSlider.set(rectBar);
		rectSlider.left = rectButtonLeft.right;
		rectSlider.right = rectButtonRight.left;

		rectCenterBox.set(rectSlider);
		rectCenterBox.top -= iSliderSpace;
		rectCenterBox.bottom += iSliderSpace;
		rectCenterBox.left += ((int) rectCenterBox.width() >> 1)
				- (iBoxWidth >> 1);
		rectCenterBox.right = rectCenterBox.left + iBoxWidth;
	}

	public void clearValue() {
		this.bNoValue = true;
		this.invalidate();
		doTimeChanged();
	}

	public void setValue(int iValue, boolean bAnimate) {
		this.bNoValue = false;
		int iMax = 0;
		if (iSliderType == STYPE_HOURS)
			iMax = (vecValues.size() - 1);
		if (iSliderType == STYPE_MINUTES)
			iMax = (vecValues.size() - 1) * 5;

		this.iAnimDelta = (iCurrentValue - iValue);

		if (iValue < 0) {
			iValue = iMax;
		}
		if (iValue > iMax) {
			iValue = 0;
		}

		this.iCurrentValue = iValue;

		this.iAnimPosX = 0;
		if (iAnimDelta > 0)
			this.iAnimPosX = 0;
		if (iAnimDelta < 0)
			this.iAnimPosX = iBoxWidth;

		if (!bAnimate) {
			this.iAnimPosX = 0;
			this.iAnimDelta = 0;
		}

		this.invalidate();
		doTimeChanged();
	}

	public int getValue() {
		return this.iCurrentValue;
	}

	private int getTextHeight() {
		return (int) (-pt.ascent() + pt.descent());
	}

	private int getTotalHeight() {
		pt.setTextSize(fTextSize);
		return iSliderBorder + iSliderSpace + iBoxBorder + getTextHeight()
				+ iBoxBorder + iSliderSpace + iSliderBorder;
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		boolean bResult = super.onKeyDown(keyCode, event);
		if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
				|| (keyCode == KeyEvent.KEYCODE_ENTER)) {
		}
		if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
			bTouchedDownBtnLeft = true;
			doClickSliderButton();
			clearTouchButtonsFlags();
		}
		if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			bTouchedDownBtnRight = true;
			doClickSliderButton();
			clearTouchButtonsFlags();
		}
		return bResult;
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		boolean bResult = super.onKeyUp(keyCode, event);
		return bResult;
	}

	private void generateValues() {
		vecValues.clear();
		if (iSliderType == STYPE_HOURS) {
			generateValuesRange(24); // 24 steps = 24 hours
		}
		if (iSliderType == STYPE_MINUTES) {
			generateValuesRange(12); // 12 steps * 5 minutes = 60minutes
		}
	}

	private void generateValuesRange(int iRange) {
		int iLeftOffset = 0;
		for (int iValue = 0; iValue < iRange; iValue++) {
			ValueBox value = new ValueBox(iValue, iLeftOffset, iBoxWidth, pt);
			vecValues.add(value);
			iLeftOffset += iBoxWidth;
		}
	}

	private void updateAnimationState() {
		// wait next frames for period of time
		if ((System.currentTimeMillis() - mLastTime) < 30)
			return;
		mLastTime = System.currentTimeMillis();

		// animate slider scrolling
		if (iAnimDelta < 0) {
			iAnimPosX -= iAnimStep;
			if (iAnimPosX < 0)
				iAnimDelta = 0;
		}

		if (iAnimDelta > 0) {
			iAnimPosX += iAnimStep;
			if (iAnimPosX > iBoxWidth)
				iAnimDelta = 0;
		}

		if (iAnimDelta == 0) {
			iAnimPosX = 0;
		}
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		updateAnimationState();

		drawBackground(canvas);

		if (!bNoValue) {
			drawSliderBars(canvas);
		}

		drawFrames(canvas);

		drawSliderButtons(canvas);
		drawArrowLeft(canvas);
		drawArrowRight(canvas);

		// call next update
		this.invalidate();
	}

	private void drawSliderBars(Canvas canvas) {
		canvas.save();
		canvas.clipRect(rectSlider);

		int index = iCurrentValue;
		if (iSliderType == STYPE_HOURS)
			index = iCurrentValue;
		if (iSliderType == STYPE_MINUTES)
			index = iCurrentValue / 5;

		// draw center box
		final ValueBox value = vecValues.get(index);
		rectBox.set(rectSlider);
		rectBox.left = rectCenterBox.left + iAnimPosX;
		rectBox.right = rectBox.left + iBoxWidth;
		drawValueBox(canvas, rectBox, value);

		// draw left boxes
		int iLeftIndex = index - 1;
		rectBox.set(rectSlider);
		rectBox.left = rectCenterBox.left - iBoxWidth + iAnimPosX;
		rectBox.right = rectBox.left + iBoxWidth;
		for (int i = 0; i < 4; i++) {
			if (iLeftIndex < 0)
				iLeftIndex = vecValues.size() - 1;
			final ValueBox valueLeft = vecValues.get(iLeftIndex);
			drawValueBox(canvas, rectBox, valueLeft);
			rectBox.left -= iBoxWidth;
			rectBox.right = rectBox.left + iBoxWidth;
			iLeftIndex--;
		}

		// draw right boxes
		int iRightIndex = index + 1;
		rectBox.set(rectSlider);
		rectBox.left = rectCenterBox.right + iAnimPosX;
		rectBox.right = rectBox.left + iBoxWidth;
		for (int i = 0; i < 4; i++) {
			if (iRightIndex > (vecValues.size() - 1))
				iRightIndex = 0;
			final ValueBox valueRight = vecValues.get(iRightIndex);
			drawValueBox(canvas, rectBox, valueRight);
			rectBox.left += iBoxWidth;
			rectBox.right = rectBox.left + iBoxWidth;
			iRightIndex++;
		}

		canvas.restore();
	}

	private void drawBackground(Canvas canvas) {
		pt.setShader(null);
		pt.setAntiAlias(true);

		// focus background
		if (this.isFocused()) {
			pt.setColor(0xffff8800);
			canvas.drawRoundRect(rectFocus, 2, 2, pt);
		}

		// background
		pt.setColor(iColorBkg);
		final int iRoundCorner = this.isFocused() ? 0 : 2;
		canvas.drawRoundRect(rect, iRoundCorner, iRoundCorner, pt);

		// draw slider background outer frame
		pt.setColor(iColorSliderBkg);
		canvas.drawRect(rectSlider, pt);

		// draw slider background inner frame
		pt.setColor(iColorValueBoxBkg);
		canvas.drawRect(rectSlider.left, rectSlider.top + 1, rectSlider.right,
				rectSlider.bottom - 1, pt);

		// center box background
		if (!bNoValue) {
			pt.setColor(iColorSliderCenterBox);
			canvas.drawRoundRect(rectCenterBox, 2, 2, pt);
		}
	}

	private boolean isCurrentBoxSelected(RectF rect) {
		if ((rect.left >= rectCenterBox.left)
				&& (rect.right <= rectCenterBox.right))
			return true;
		return false;
	}

	private void drawValueBox(Canvas canvas, RectF rect, ValueBox value) {
		final boolean bSelected = isCurrentBoxSelected(rect);

		// draw value number
		pt.setShader(null);
		pt.setAntiAlias(true);
		pt.setFakeBoldText(true);
		pt.setTypeface(tfMono);
		pt.setUnderlineText(false);

		pt.setTextSize(fTextSizeSmall);
		if (bSelected)
			pt.setTextSize(fTextSize);

		if (iAnimPosX != 0)
			pt.setMaskFilter(filterBlur);

		final int iNumberWidth = (bSelected) ? value.iStrWidth
				: value.iStrWidthSmall;

		int iTextPosX = (int) rect.right - iNumberWidth;
		int iTextPosY = (int) rect.bottom + (int) (-pt.ascent())
				- getTextHeight();

		iTextPosX -= ((int) rect.width() >> 1) - (iNumberWidth >> 1);
		iTextPosY -= ((int) rect.height() >> 1) - (getTextHeight() >> 1);

		iTextPosY += 1;

		pt.setColor((bSelected) ? iColorValueBoxTextSelected
				: iColorValueBoxText);
		canvas.drawText(value.strValue, iTextPosX, iTextPosY, pt);

		pt.setMaskFilter(null);
	}

	private void drawSliderButtons(Canvas canvas) {
		pt.setShader(null);
		pt.setAntiAlias(true);
		pt.setFakeBoldText(true);
		pt.setTypeface(tfMono);
		pt.setTextSize(fTextSize);
		pt.setUnderlineText(false);

		pt.setColor((bTouchedDownBtnLeft) ? iColorLightBkg : iColorBkg);
		canvas.drawRoundRect(rectButtonLeft, 2, 2, pt);

		pt.setColor((bTouchedDownBtnRight) ? iColorLightBkg : iColorBkg);
		canvas.drawRoundRect(rectButtonRight, 2, 2, pt);
	}

	private void drawFrames(Canvas canvas) {
		pt.setColor(iColorSliderBkg);
		pt.setStrokeWidth(0);
		pt.setAntiAlias(false);

		// slider frame
		canvas.drawLine(rectSlider.left, rectSlider.top, rectSlider.left,
				rectSlider.bottom, pt);
		canvas.drawLine(rectSlider.right - 1, rectSlider.top,
				rectSlider.right - 1, rectSlider.bottom, pt);

		// center box frame
		canvas.drawLine(rectCenterBox.left - 1, rectSlider.top,
				rectCenterBox.left - 1, rectSlider.bottom, pt);
		canvas.drawLine(rectCenterBox.right, rectSlider.top,
				rectCenterBox.right, rectSlider.bottom, pt);
	}

	private void drawArrowLeft(Canvas canvas) {
		final int iH = 8;
		final int iH2 = 4;
		final int iV = 14;
		final int iX = (int) rectButtonLeft.centerX();
		final int iY = (int) rectButtonLeft.centerY();
		pt.setAntiAlias(true);
		pt.setStrokeCap(Paint.Cap.ROUND);
		pt.setStrokeWidth(6);
		pt.setColor(iColorButtonArrows);
		canvas.drawLine(iX - iH, iY, iX + iH2, rectButtonLeft.top + iV, pt);
		canvas.drawLine(iX - iH, iY, iX + iH2, rectButtonLeft.bottom - iV, pt);
	}

	private void drawArrowRight(Canvas canvas) {
		final int iH = 8;
		final int iH2 = 4;
		final int iV = 14;
		final int iX = (int) rectButtonRight.centerX();
		final int iY = (int) rectButtonRight.centerY();
		pt.setAntiAlias(true);
		pt.setStrokeCap(Paint.Cap.ROUND);
		pt.setStrokeWidth(6);
		pt.setColor(iColorButtonArrows);
		canvas.drawLine(iX - iH2, rectButtonRight.top + iV, iX + iH, iY, pt);
		canvas.drawLine(iX - iH2, rectButtonRight.bottom - iV, iX + iH, iY, pt);
	}

	@Override
	protected void onFocusChanged(boolean gainFocus, int direction,
			Rect previouslyFocusedRect) {
		super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
		invalidate();
	}

	private void doClickSliderButton() {
		int iStep = 0;
		if (iSliderType == STYPE_MINUTES)
			iStep = 5;
		if (iSliderType == STYPE_HOURS)
			iStep = 1;
		if (bTouchedDownBtnLeft) {
			int iValue = getValue() - iStep;
			setValue(iValue, true);
		}
		if (bTouchedDownBtnRight) {
			int iValue = getValue() + iStep;
			setValue(iValue, true);
		}
	}

	private boolean touchInSliderButtonsArea(MotionEvent event) {
		clearTouchButtonsFlags();
		if (rectButtonLeft.contains(event.getX(), event.getY())) {
			bTouchedDownBtnLeft = true;
			return true;
		}
		if (rectButtonRight.contains(event.getX(), event.getY())) {
			bTouchedDownBtnRight = true;
			return true;
		}
		return false;
	}

	private void clearTouchButtonsFlags() {
		bTouchedDownBtnLeft = false;
		bTouchedDownBtnRight = false;
	}

	private void clearTouchSliderFlags() {
		bTouchedDownSlider = false;
	}

	private boolean touchInSliderArea(MotionEvent event) {
		clearTouchSliderFlags();
		if (rectSlider.contains(event.getX(), event.getY())) {
			bTouchedDownSlider = true;
			iTouchedSliderPosX = (int) event.getX();
			return true;
		}
		return false;
	}

	private void touchSliderMoved(MotionEvent event) {
		if (bTouchedDownSlider) {
			final int iDelta = (iTouchedSliderPosX - (int) event.getX());

			if (Math.abs(iDelta) >= (iBoxWidth * 0.7)) {
				// move right
				if (iDelta > 0)
					bTouchedDownBtnRight = true;
				// move left
				if (iDelta < 0)
					bTouchedDownBtnLeft = true;

				iTouchedSliderPosX = (int) event.getX();
				invalidate();
				doClickSliderButton();
				clearTouchButtonsFlags();
			}
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		boolean bHandled = false;
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			if (touchInSliderButtonsArea(event) || touchInSliderArea(event)) {
				bHandled = true;
				invalidate();
			}
		}
		if (event.getAction() == MotionEvent.ACTION_CANCEL) {
			clearTouchButtonsFlags();
			clearTouchSliderFlags();
			bHandled = true;
			invalidate();
		}
		if (event.getAction() == MotionEvent.ACTION_UP) {
			bHandled = true;
			invalidate();
			doClickSliderButton();
			clearTouchButtonsFlags();
			clearTouchSliderFlags();
			invalidate();
		}
		if (event.getAction() == MotionEvent.ACTION_MOVE) {
			touchSliderMoved(event);
			bHandled = true;
			invalidate();
		}
		return bHandled;
	}

}
